﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.IO;
using GeneralDFS.BusinessLogical.Nodes;
using GeneralDFS.BusinessLogical.Protocol;
using GeneralDFS.Common.IO;
using GeneralDFS.DataEntity.Enum;
using GeneralDFS.DataEntity.IO;
using GeneralDFS.DataEntity.Nodes;

namespace GeneralDFS.Client
{
    public class DFSOutputStream
    {
        private readonly String _filePath;
        private readonly Client _client;
        private readonly NameNodeInfo _nameNodeInfo;
        private LocatedBlock _currentBlock;
        private List<LocatedBlock> _locatedBlocks;
        private int _index;
        private NetworkStream _networkStream;

        public DFSOutputStream(String src, Client client)
        {
            if (!PathHelper.CheckPathVaild(src))
                throw new ArgumentException("invaild filepath");

            _filePath = src;
            _client = client ?? new Client();

            _nameNodeInfo = _client.FindNameNode(src);
            if (_nameNodeInfo == null)
            {
                throw new Exception("Client can not get NameNodeInfo");
            }
            var remoteObject = NameNode.GetInstance(_nameNodeInfo);
            if (NameNode.IsInstanceValid(remoteObject))
            {
                remoteObject.GetNodeByPath(_filePath);
                _locatedBlocks = remoteObject.GetLocatedBlocks(_filePath);
            }
            if (_locatedBlocks == null || _locatedBlocks.Count == 0)
                throw new Exception("file have 0 block");
        }

        public void Seek(long offset)
        {
            _index = (int)offset / _client.GetConfig().DetailConfig.BlockSize;
            if (offset >
                _locatedBlocks[_locatedBlocks.Count - 1].FileBlockMetaData.Offset +
                _locatedBlocks[_locatedBlocks.Count - 1].FileBlockMetaData.Length)
            {
                throw new ArgumentOutOfRangeException("offset too larger");
            }
            _currentBlock = _locatedBlocks[_index];
            var client = new TcpClient(_currentBlock.DataNodeIds[0].Ip, _currentBlock.DataNodeIds[0].TcpPort);
            _networkStream = client.GetStream();
        }

        public void ReadBlock(FileStream stream, long offset, out FileBlockMetaData fileBlockMetaData)
        {
            Seek(offset);
            ProcessOp.WriteOp(new BinaryWriter(_networkStream),Op.ReadBlock);
            DataTranslater.WriteHead(new BinaryWriter(_networkStream), _currentBlock.FileBlockMetaData.Guid);
            DataTranslater.ReadBlock(out fileBlockMetaData, new BinaryReader(_networkStream), stream,_client.GetConfig().DetailConfig.BuffSize);
            _networkStream.Dispose();
        }

        public void ReadFile(String localPath)
        {
            if (File.Exists(localPath))
                File.Delete(localPath);

            var stream = File.Create(localPath);
            var count = _locatedBlocks.Count;
            stream.Seek(0, SeekOrigin.Begin);

            if (_locatedBlocks[0].FileBlockMetaData.IsSmallFile)
            {
                ReadSmallBlock(stream);
            }
            else
            {
                for (var i = 0; i < count; i++)
                {
                    FileBlockMetaData fileBlockMetaData = null;
                    ReadBlock(stream, i * _client.GetConfig().DetailConfig.BlockSize, out fileBlockMetaData);
                }
            }
            stream.Dispose();
        }

        private void ReadSmallBlock(FileStream stream)
        {
            _currentBlock = _locatedBlocks[0];
            var client = new TcpClient(_currentBlock.DataNodeIds[0].Ip, _currentBlock.DataNodeIds[0].TcpPort);
            _networkStream = client.GetStream();
            ProcessOp.WriteOp(new BinaryWriter(_networkStream), Op.ReadSmallBlock);
            DataTranslater.WriteHead(new BinaryWriter(_networkStream), _currentBlock.FileBlockMetaData.Guid);
            DataTranslater.ReadSmallBlock(stream, new BinaryReader(_networkStream),_currentBlock.FileBlockMetaData);
            _networkStream.Dispose();
        }
    }
}
